package cn.matrix42.potoo.jdbc.dialect.oracle;

import cn.matrix42.potoo.jdbc.dialect.AbstractDialect;

import java.util.Arrays;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

public class OracleDialect extends AbstractDialect {

    private static final String DRIVER = "oracle.jdbc.OracleDriver";
    private static final String URL_PATTERN = "jdbc:oracle:thin:@//%s:%d/%s";

    @Override
    public String dialectName() {
        return "Oracle";
    }

    @Override
    public String getLimitClause(long limit) {
        return "FETCH FIRST " + limit + " ROWS ONLY";
    }

    @Override
    public String quoteIdentifier(String identifier) {
        return identifier;
    }

    @Override
    public Optional<String> getUpsertStatement(String tableName, String[] fieldNames, String[] uniqueKeyFields) {
        String sourceFields =
            Arrays.stream(fieldNames)
                .map(f -> ":" + f + " " + quoteIdentifier(f))
                .collect(Collectors.joining(", "));

        String onClause =
            Arrays.stream(uniqueKeyFields)
                .map(f -> "t." + quoteIdentifier(f) + "=s." + quoteIdentifier(f))
                .collect(Collectors.joining(" and "));

        final Set<String> uniqueKeyFieldsSet =
            Arrays.stream(uniqueKeyFields).collect(Collectors.toSet());
        String updateClause =
            Arrays.stream(fieldNames)
                .filter(f -> !uniqueKeyFieldsSet.contains(f))
                .map(f -> "t." + quoteIdentifier(f) + "=s." + quoteIdentifier(f))
                .collect(Collectors.joining(", "));

        String insertFields =
            Arrays.stream(fieldNames)
                .map(this::quoteIdentifier)
                .collect(Collectors.joining(", "));

        String valuesClause =
            Arrays.stream(fieldNames)
                .map(f -> "s." + quoteIdentifier(f))
                .collect(Collectors.joining(", "));

        // if we can't divide schema and table-name is risky to call quoteIdentifier(tableName)
        // for example [tbo].[sometable] is ok but [tbo.sometable] is not
        String mergeQuery =
            " MERGE INTO "
                + tableName
                + " t "
                + " USING (SELECT "
                + sourceFields
                + " FROM DUAL) s "
                + " ON ("
                + onClause
                + ") "
                + " WHEN MATCHED THEN UPDATE SET "
                + updateClause
                + " WHEN NOT MATCHED THEN INSERT ("
                + insertFields
                + ")"
                + " VALUES ("
                + valuesClause
                + ")";

        return Optional.of(mergeQuery);
    }

    @Override
    public String getDriver() {
        return DRIVER;
    }

    @Override
    public String getUrlPattern() {
        return URL_PATTERN;
    }
}
